package com.ke.rule.base;

import com.google.common.base.Splitter;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 功能部件规格
 * 
 * TestValidator, 1
 * TestValidator first
 * TestValidator last
 * TestValidator before RelativeValidator withParam {k1=v1}
 * TestValidator after RelativeValidator withParam {k1=v1} applyTo field:field1
 * 
 * @author guyu
 */
public final class FuncUnitSpec {
  private static final Logger logger = LoggerFactory.getLogger(FuncUnitSpec.class);
  
  //老版本正则式: ^(功能部件名称)(, 优先级序号)?(, ApplyToType: ApplyToWhich)?$，其中ApplyToType为枚举(entity|field|action|event)
  private static final Pattern OLD_REGEX_PATTERN = Pattern.compile("^([A-Z]\\w+)\\s*(,\\s*([\\d]+))?\\s*(,\\s*(entity|field|action)\\s*:(.+))?$");
  private static final int MATCH_GROUP_NAME = 1;
  private static final int OLD_MATCH_GROUP_PRIORITY = 3;
  private static final int OLD_MATCH_GROUP_APPLY_TO_TYPE = 5;
  private static final int OLD_MATCH_GROUP_APPLY_TO_WHICH = 6;
  
  // 新版本正则式: ^(功能部件名称)(优先级序号)?(position relativeFuncUnit?)?(withParam {k=v})?(applyTo applyToType: applyToWhich)?$
  // 其中:position为枚举(first|last|before|after), applyToType为枚举(entity|field|action)
  private static final Pattern REGEX_PATTERN = Pattern.compile("^([A-Z]\\w+)\\s*([\\d]+)?\\s*((first|last|before|after)\\s*([A-Z]\\w+)?)?\\s*(withParam\\s*\\{([a-zA-Z_0-9=,]+)\\})?\\s*(applyTo\\s*(entity|field|action)\\s*:(.+))?$");
  private static final int MATCH_GROUP_PRIORITY = 2;
  private static final int MATCH_GROUP_POSITION = 4;
  private static final int MATCH_GROUP_RELATIVE_FUNC_UNIT = 5;
  private static final int MATCH_GROUP_WITH_PARAM = 7;
  private static final int MATCH_GROUP_APPLY_TO_TYPE = 9;
  private static final int MATCH_GROUP_APPLY_TO_WHICH = 10;
  
  private String original;      // 部件规格原文
  private String name;          // 功能部件名称(class.simpleName)
  private String id;            // 同一个部件在一个阶段中可能会被执行多次，此时以Id来区别，Id为空时则同名部件会被去重
  private Class<? extends IFuncUnit> klazz; // 类名
  private FuncUnitType type;            // 部件类型
  private PrioritizeMode prioritizeMode;// 优先级确定模式
  private Integer priorityNbr;          // 优先级序号, NullsLast, 升序
  private String relativeName;          // 优先级的相对部件名称
  private Map<String, String> withParams;   // 入参
  private IFuncUnit.ApplyTo applyTo;        // 应用

  /**
   * @param id 自定义规则的Id
   * @param code 自定义规则的对照码
   * @param name 部件类名称，注：类名，非spring的component name
   * @param applyTo 应用实体及字段
   */
  public FuncUnitSpec(String id, String code, String name, FuncUnitApplyTo applyTo) {
    super();
    this.id = id;
    this.name = name;

    if (StringUtils.isNotBlank(code)) {
      withParams = MapBuilder.of("code", code);
    }
    if (null != applyTo) {
      this.applyTo = applyTo;
    }
  }
  
  private FuncUnitSpec(String original, String name) {
    super();
    this.original = original;
    this.name = name;
  }

  public String getOriginal() {
    return original;
  }

  public String getName() {
    return name;
  }

  public String getId() {
    return id;
  }

  /**
   * 调用时延分解的名称
   */
  public String getBreakdownName() {
    if (StringUtils.isNotBlank(original)) {
      return original;
    }
    if (StringUtils.isBlank(id)) {
      return name;
    }
    return String.join(" ", name, id);
  }
  
  public Class<? extends IFuncUnit> getKlazz() {
    return klazz;
  }

  public void setKlazz(Class<? extends IFuncUnit> clazz) {
    this.klazz = clazz;
    this.type = FuncUnitType.of(clazz);
  }

  public FuncUnitType getType() {
    return type;
  }

  public PrioritizeMode getPrioritizeMode() {
    return prioritizeMode;
  }

  public void setPrioritizeMode(PrioritizeMode prioritizeMode) {
    this.prioritizeMode = prioritizeMode;
  }

  public Integer getPriorityNbr() {
    return priorityNbr;
  }

  public void setPriorityNbr(Integer priorityNbr) {
    this.priorityNbr = priorityNbr;
  }

  public String getRelativeName() {
    return relativeName;
  }

  public void setRelativeName(String relativeName) {
    this.relativeName = relativeName;
  }

  public IFuncUnit.ApplyTo getApplyTo() {
    return applyTo;
  }

  public void setApplyTo(IFuncUnit.ApplyTo applyTo) {
    this.applyTo = applyTo;
  }

  public Map<String, String> getWithParams() {
    return withParams;
  }

  public void setWithParams(Map<String, String> withParams) {
    this.withParams = withParams;
  }


  @Override
  public int hashCode() {
    return Objects.hash(id, name);
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    FuncUnitSpec other = (FuncUnitSpec) obj;
    return Objects.equals(id, other.id) && Objects.equals(name, other.name);
  }
  
  @Override
  public String toString() {
    return "FuncUnitSpec [original=" + original + ", name=" + name + ", id=" + id + ", klazz="
        + klazz + ", type=" + type + ", prioritizeMode=" + prioritizeMode + ", priorityNbr="
        + priorityNbr + ", relativeName=" + relativeName + ", withParams=" + withParams
        + ", applyTo=" + applyTo + "]";
  }
  
  /**
   * 解析功能部件规格文本并实例化对象
   * @param funcUnitSpec
   * @return
   */
  public static FuncUnitSpec parse(String funcUnitSpec) {
    boolean isOldVersion = false;
    Matcher matcher = FuncUnitSpec.REGEX_PATTERN.matcher(funcUnitSpec);
    
    if (!matcher.matches()) {
      matcher = FuncUnitSpec.OLD_REGEX_PATTERN.matcher(funcUnitSpec);
      if (!matcher.matches()) {
        throw new IllegalArgumentException(String.format("\"%s\" does not match regex expression \"%s\"",
            funcUnitSpec,
            "^(功能部件名称)(优先级序号)?(position relativeFuncUnit?)?(withParam {k=v})?(applyTo applyToType: applyToWhich)?$"));
      }
      if (logger.isDebugEnabled()) {
        logger.debug("please Use the new version syntax to define FuncUnitSpec ASAP: {}", funcUnitSpec);
      }
      isOldVersion = true;
    }
    
    FuncUnitSpec spec = new FuncUnitSpec(funcUnitSpec, matcher.group(MATCH_GROUP_NAME));
    if (isOldVersion) {
      return parseOldVersion(spec, matcher);
    }
    
    String priority = matcher.group(MATCH_GROUP_PRIORITY);
    if (null != priority) {
      spec.setPrioritizeMode(PrioritizeMode.ASSIGN);
      spec.setPriorityNbr(new Integer(priority));
    } else {
      String position = matcher.group(MATCH_GROUP_POSITION);
      if (null != position) {
        PrioritizeMode mode = EnumUtils.getEnumIgnoreCase(PrioritizeMode.class, position);
        spec.setPrioritizeMode(mode);
        
        if (mode.isRelative()) {
          String relativeName = matcher.group(MATCH_GROUP_RELATIVE_FUNC_UNIT);
          if (null == relativeName) {
            throw new IllegalArgumentException("Missing relative func unit name");
          }
          spec.setRelativeName(relativeName);
        }
      }
    }
    
    String withParam = matcher.group(MATCH_GROUP_WITH_PARAM);
    if (null != withParam) {
      spec.setWithParams(Splitter.on(",").withKeyValueSeparator("=").split(withParam));
    }
    
    String applyToType = matcher.group(MATCH_GROUP_APPLY_TO_TYPE);
    if (null != applyToType) {
      FuncUnitApplyTo applyTo = new FuncUnitApplyTo();
      applyTo.setType(ApplyToType.of(applyToType));
      
      String applyToWhich = matcher.group(MATCH_GROUP_APPLY_TO_WHICH);
      if (null != applyToWhich) {
        applyTo.setWhich(applyToWhich.trim());
      }
      
      spec.setApplyTo(applyTo);
    }
    
    return spec;
  }
  
  /**
   * 解析老的规格定义
   * @param spec
   * @param matcher
   */
  private static FuncUnitSpec parseOldVersion(FuncUnitSpec spec, Matcher matcher) {
    String priority = matcher.group(OLD_MATCH_GROUP_PRIORITY);
    if (null != priority) {
      spec.setPriorityNbr(new Integer(priority));
    }
    
    String applyToType = matcher.group(OLD_MATCH_GROUP_APPLY_TO_TYPE);
    if (null != applyToType) {
      FuncUnitApplyTo applyTo = new FuncUnitApplyTo();
      applyTo.setType(ApplyToType.of(applyToType));
      
      String applyToWhich = matcher.group(OLD_MATCH_GROUP_APPLY_TO_WHICH);
      if (null != applyToWhich) {
        applyTo.setWhich(applyToWhich.trim());
      }
      
      spec.setApplyTo(applyTo);
    }
    
    return spec;
  }
  
  private static final Comparator<FuncUnitSpec> priorityNullsLastComparator = Comparator.comparing(FuncUnitSpec::getPriorityNbr,
      Comparator.nullsLast(Comparator.naturalOrder()));

  /**
   * 去重，并按优先级排序，nullsLast
   * @param funcUnitSpecList
   */
  public static List<FuncUnitSpec> distinctAndOrder(List<FuncUnitSpec> funcUnitSpecList) {
    List<FuncUnitSpec> results = funcUnitSpecList.stream().distinct().collect(Collectors.toList());
    results.sort(priorityNullsLastComparator);
    
    // 针对null处理方位词
    List<FuncUnitSpec> nulls = results.stream().filter(spec -> null == spec.getPriorityNbr() && null != spec.getPrioritizeMode()).collect(Collectors.toList());

    // 按first/last/before/after这个优先次序处理方位词，其中first/after按反序
    List<FuncUnitSpec> firsts = filterByPrimode(nulls, PrioritizeMode.FIRST);
    firsts.stream().forEach(spec -> {
      moveFirst(spec, results);
    });
    
    List<FuncUnitSpec> lasts = filterByPrimode(nulls, PrioritizeMode.LAST);
    lasts.stream().forEach(spec -> {
      moveLast(spec, results);
    });
    
    List<FuncUnitSpec> befores = filterByPrimode(nulls, PrioritizeMode.BEFORE);
    befores.stream().forEach(spec -> {
      moveBefore(spec, results);
    });
    
    List<FuncUnitSpec> afters = filterByPrimode(nulls, PrioritizeMode.AFTER);
    afters.stream().forEach(spec -> {
      moveAfter(spec, results);
    });
    
    return results;
  }
  
  /**
   * 过滤出符合排序模式的列表，并按是否逆序输出
   * @param specs
   * @param mode
   * @param reversed
   * @return
   */
  private static List<FuncUnitSpec> filterByPrimode(List<FuncUnitSpec> specs, PrioritizeMode mode) {
    List<FuncUnitSpec> results = specs.stream().filter(sp -> mode.equals(sp.getPrioritizeMode())).collect(Collectors.toList());
    if (mode.isNeedToReverse()) {
      Collections.reverse(results);
    }
    return results;
  }
  
  /**
   * 移至首位
   * @param spec
   * @param list
   */
  private static void moveFirst(FuncUnitSpec spec, List<FuncUnitSpec> list) {
    list.remove(spec);
    list.add(0, spec);
  }
  
  /**
   * 移至末位
   * @param spec
   * @param list
   */
  private static void moveLast(FuncUnitSpec spec, List<FuncUnitSpec> list) {
    list.remove(spec);
    list.add(spec);
  }
  
  /**
   * 规格挪动
   * @param spec
   * @param list
   */
  private static void moveBefore(FuncUnitSpec spec, List<FuncUnitSpec> list) {
    int relativeSpecPos = ListUtils.indexOf(list, e -> spec.getRelativeName().equals(e.getName()));
    if (relativeSpecPos < 0) {
      logger.warn("{} not found, {} will remain in its current position", spec.getRelativeName(), spec.getName());
      return;
    }
    
    int specPos = ListUtils.indexOf(list, e -> spec.getName().equals(e.getName()));
    if (specPos < 0) {
      throw new IllegalArgumentException(String.format("%s not found", spec.getName()));
    }
    
    if (specPos + 1 == relativeSpecPos) {
      return;
    }
    
    list.remove(spec);
    if (specPos < relativeSpecPos) {
      list.add(relativeSpecPos-1, spec);
    } else {
      list.add(relativeSpecPos, spec);
    }
  }
  
  /**
   * 规格挪动
   * @param spec
   * @param list
   */
  private static void moveAfter(FuncUnitSpec spec, List<FuncUnitSpec> list) {
    int relativeSpecPos = ListUtils.indexOf(list, e -> spec.getRelativeName().equals(e.getName()));
    if (relativeSpecPos < 0) {
      logger.warn("{} not found, {} will remain in its current position", spec.getRelativeName(), spec.getName());
      return;
    }
    
    int specPos = ListUtils.indexOf(list, e -> spec.getName().equals(e.getName()));
    if (specPos < 0) {
      throw new IllegalArgumentException(String.format("%s not found", spec.getName()));
    }
    
    if (specPos - 1 == relativeSpecPos) {
      return;
    }
    
    list.remove(spec);
    if (specPos < relativeSpecPos) {
      list.add(relativeSpecPos, spec);
    } else {
      list.add(relativeSpecPos+1, spec);
    }
  }
}
